﻿using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace ThematicClustering.Utils
{
    /// <summary>
    /// Műholdképek sávok szerinti pixel mátrixainak tároló osztálya
    /// </summary>
    public class ImageLayers
    {
        #region Fields

        private byte[, ,] pLayers;
        private int pWidth;
        private int pHeight;
        private int pLayerCount;
        private int pDb;
        private SquareMatrix pNu;
        private SquareMatrix pCovariance;

        #endregion 
       
        #region Properties

        /// <summary>
        ///  A tárolt műholdkép szélessége.
        /// </summary>
        public int Width
        {
            get { return pWidth; }
            private set { pWidth = value; }
        }

        /// <summary>
        /// A tárolt műholdkép magassága.
        /// </summary>
        public int Height
        {
            get { return pHeight; }
            private set { pHeight = value; }
        }

        /// <summary>
        /// A tárolt műholkép sávjainak száma.
        /// </summary>
        public int LayerCount
        {
            get { return pLayerCount; }
            private set { pLayerCount = value; }
        }

        /// <summary>
        /// A tárolt műholdkép sávjainak multidimenziós byte mátrixa.
        /// </summary>
        public byte[, ,] Layers
        {
            get { return pLayers; }
            private set { pLayers = value; }
        }

        /// <summary>
        /// Mintapixelek darabszáma
        /// </summary>
        private int N
        {
            get { return Width * Height; }
        }

        /// <summary>
        /// Fehér mintapixelek darabszáma
        /// </summary>
        public int Db
        {
            get
            {
                return pDb;
            }
            set
            {
                pDb = value;
            }
        }

        /// <summary>
        /// Várható érték vektor. 
        /// <sáv>-<várható érték> szerkezetben.
        /// </summary>
        public SquareMatrix Nu
        {
          get { return pNu; }
          private set { pNu = value; }
        }

        /// <summary>
        /// Kovarianciamátrix
        /// </summary>
        public SquareMatrix Covariance
        {
            get { return pCovariance; }
            private set { pCovariance = value; }
        }

        #endregion

        #region Contructors

        /// <summary>
        /// Constructor üres ImageLayers létrehozásához
        /// </summary>
        /// <param name="L">Layer szám</param>
        /// <param name="W">Szélesség</param>
        /// <param name="H">Magasság</param>
        public ImageLayers(int L, int W, int H)
        {
            Width = W;
            Height = H;
            LayerCount = L;
            Layers = new byte[LayerCount, W, H];
        }

        /// <summary>
        /// Konstruktor ImageLayers létrehozásához egy képfájlból.
        /// </summary>
        /// <param name="picture">3 sávos bemeneti kép</param>
        public ImageLayers(Bitmap picture)
        {
            Width = picture.Width;
            Height = picture.Height;
            LayerCount = 3;

            Layers = new byte[LayerCount, Width, Height];
            int step = 1;
            switch (picture.PixelFormat)
            {
                case PixelFormat.Format24bppRgb:
                    step = 3;
                    break;
                case PixelFormat.Format8bppIndexed:
                    step = 1;
                    break;
            }

            BitmapData pictureData = picture.LockBits(new System.Drawing.Rectangle(0, 0, picture.Width, picture.Height), ImageLockMode.ReadWrite, picture.PixelFormat);
            unsafe
            {
                int remain = pictureData.Stride - pictureData.Width * step;
                byte* ptr = (byte*)pictureData.Scan0;

                for (int j = 0; j < Height; ++j)
                {
                    for (int i = 0; i < Width; ++i)
                    {
                        Layers[0, i, j] = ptr[2];
                        Layers[1, i, j] = ptr[1];
                        Layers[2, i, j] = ptr[0];

                        ptr += step;
                    }
                    ptr += remain;
                }
            }
            picture.UnlockBits(pictureData);
        }

        #endregion

        #region Member Methods

        /// <summary>
        /// ImageLayersből bitmap képbe átalakító.
        /// </summary>
        /// <param name="picture">Generált kimenő kép</param>
        /// <returns>Generált kép</returns>
        public Bitmap CreateImage()
        {
            Bitmap picture = new Bitmap(Width, Height);

            BitmapData pictureData = picture.LockBits(new System.Drawing.Rectangle(0, 0, picture.Width, picture.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            unsafe
            {
                int remain = pictureData.Stride - pictureData.Width * 3;
                byte* ptr = (byte*)pictureData.Scan0;

                for (int j = 0; j < Height; ++j)
                {
                    for (int i = 0; i < Width; ++i)
                    {
                        ptr[0] = Layers[2, i, j];
                        ptr[1] = Layers[1, i, j];
                        ptr[2] = Layers[0, i, j];

                        ptr += 3;
                    }
                    ptr += remain;
                }

            }
            picture.UnlockBits(pictureData);

            return picture;
        }

        /// <summary>
        /// Várható érték vektor.(mean) Csak a maszkon fehér színű pixelekre számolja az eredeti kép alapján.
        /// nu = 1/N * Summa(i:1-Width)Summa(j:1-Height) (i,j)-pixel érték
        /// <param name="Picture">A maszkolandó eredeti kép</param>
        /// </summary>
        private void GenerateNu(ImageLayers Picture)
        {
            double[] sum = new double[LayerCount];
            int Darab = 0;

            for (int i = 0; i < Width; ++i)
            {
                for (int j = 0; j < Height; ++j)
                {
                    if (Layers[0, i, j] == 255)
					{
						for (int k = 0; k < LayerCount; k++)
                        {
                            sum[k] += Picture.Layers[k, i, j];
                            ++Darab;
                        }
                    }
                }
            }
            for (int i = 0; i < LayerCount; ++i)
            {
                sum[i] /= Darab;
            }
            
            Nu = new SquareMatrix(sum, LayerCount);
        }

        /// <summary>
        /// Covarianciamátrix kiszámolása. Csak a maszkon fehér színű pixelekre számolja az eredeti kép alapján.
        /// 1/(N-1) * Summa(i:1-Width)Summa(j:1-Height) [(fx - nu)*(fk-nu)transzponált]
        /// <param name="Picture">A maszkolandó eredeti kép</param>
        /// </summary>
        private void GenerateCovariance(ImageLayers Picture)
        {
            double[,] s = new double[LayerCount, LayerCount];
            int Darab = 0;
            for (int j = 0; j < Height; ++j)
            {
                for (int i = 0; i < Width; ++i)
                {
					if (Layers[0, i, j] == 255)
					{
                        ++Darab;
                    	double[] tmp = new double[LayerCount];
                    	for (int k = 0; k < LayerCount; k++)
                    	{
                            tmp[k] = Picture.Layers[k, i, j] - Nu[k, 0];
	                    }

    	                //matrix szorzas
        	            for (int k = 0; k < LayerCount; k++)
            	        {
                	        for (int l = 0; l < LayerCount; l++)
                    	    {
                    	        s[l, k] += tmp[l] * tmp[k];
                        	}
						}
                    }
                }
            }
            
            for (int j = 0; j < LayerCount; ++j)
            {
                for (int i = 0; i < LayerCount; ++i)
                {
                    s[i, j] /= Darab - 1;
                }
            }
            Covariance = new SquareMatrix(s);
        }

        /// <summary>
        /// Visszaadja az j-edik sor i-edik oszlopában található
        /// pixel RGB vektorát.
        /// </summary>
        /// <param name="i">Oszlop szám</param>
        /// <param name="j">Sor szám</param>
        /// <returns>RGB vektor</returns>
        public double[] GetRGBVector(int i, int j)
        {
            double[] rgbVect = new double[LayerCount];
            for (int k = 0; k < LayerCount; k++)
            {
                rgbVect[k] = Layers[k, i, j];
            }
            return rgbVect;
        }

        /// <summary>
        /// Beállítja a j-edik sor i-edik oszlopában található
        /// pixel RGB vektorát.
        /// </summary>
        /// <param name="i">Oszlop szám</param>
        /// <param name="j">Sor szám</param>
        /// <param name="rgb">RGB vektor</param>
        public void SetRGBVector(int i, int j, byte[] rgb)
        {
            for (int k = 0; k < LayerCount; k++)
            {
                Layers[k, i, j] = rgb[k];
            }
        }


        /// <summary>
        /// CSAK a pattern képre hívható!
        /// A pattern képet felhasználva maszkolva az eredeti képet, kiszámolja a nű és a cov vektort
        /// <param name="Picture">A maszkolandó eredeti kép</param>
        /// </summary>
        public void MaskedStats(ImageLayers Picture)
        {
            GenerateNu(Picture);
            GenerateCovariance(Picture);
        }

        #endregion
    }
}
